import {
  IArcOfCircleParams,
  ICircleParams,
  ICurve,
  IEllipseParams,
  MathTool,
  SceneGraph,
} from '@amcax/base';
import { Object3D, Scene, Vector2, Vector2tuple, Vector3 } from 'three-legacy';
import { ctx2, docMgr } from '@app-cad/module';
import { DimLinear } from '../dimension/DimLinear';
import {
  AnnotationType,
  AnnotationUpdateParams,
  DimAngleAttrs,
} from '../dimension/types';
import {
  AnnotationData,
  CurveMap,
  DimInfo,
  DimMap,
  DimObject,
  groupConnectedElements,
} from './type';
import { DimDiameterTool } from '../dimension/DimDiameter';
import { DimRadiusTool } from '../dimension/DimRadiusTool';
import { DimAngleTool } from '../dimension/DimAngleTool';
import { getAngle, parseAxis, isParallel } from '@app-cad/common/utils';
import { DimEllipseTool } from '../dimension/DimEllipseRadius';
import { isDriven } from './tools';
import { sketchEventCenter } from '@app-base/environments/nextcad/events/SketchEventCenter';
import { sketchStore } from '@app-base/common/utils/sketchTools/useSketchStore';
import { isEmpty } from 'lodash';

export default class DimRender {
  private sketMap: CurveMap = new Map();
  private dimMap: DimMap = new Map();
  private dimObj: { [key: string]: string } = {};
  static instance: DimRender;
  public dimensionGroup: Scene;
  public dimensionPreviewGroup: Scene;
  private previewDim: DimObject; // 预览的尺寸
  private isPreview: boolean = false;
  private preCallback: (info: DimInfo) => void;
  private equalConArr: string[][] = [];
  private labelBlurFun: (
    curvesParams: AnnotationUpdateParams,
  ) => Promise<void> = async () => {};

  constructor() {
    this.init();
  }

  getCurrentSketchCurve() {
    const args = docMgr.documentObjects;
    const sketchId = sketchEventCenter().getSketchInfo().sketchId;
    const temp = args.filter((item) => !item.type.includes('Basic'));
    let childTemp = null;
    for (let i = 0; i < temp.length; i++) {
      const children = temp[i].children;
      for (let j = 0; j < children.length; j++) {
        const child = children[j];
        if (child.dxid === sketchId) {
          childTemp = child.children.find((item) => item.type === 'SketchRoot');
          break;
        }
      }
    }
    this.setSkeMap(childTemp?.children || []);
  }

  getEqualConArr() {
    return this.equalConArr;
  }

  getSketMap() {
    return this.sketMap;
  }

  getDimObj() {
    return this.dimObj;
  }

  getDimMap() {
    return this.dimMap;
  }

  clear(): void {
    this.remove('all');
  }

  setLabelBlurFun(
    fun: (
      curvesParams: AnnotationUpdateParams,
    ) => Promise<void> = async () => {},
  ) {
    this.labelBlurFun = fun;
  }

  static getInstance(): DimRender {
    if (!this.instance) {
      this.instance = new DimRender();
    }
    return this.instance;
  }

  init(): void {
    this.dimensionGroup = new Scene();
    this.dimensionGroup.name = 'dimensionGroup';
    this.dimensionPreviewGroup = new Scene();
    this.dimensionPreviewGroup.name = 'dimensionPreviewGroup';
    SceneGraph.addToGroup(ctx2.cadScene.workGroup, this.dimensionGroup);
    SceneGraph.addToGroup(ctx2.cadScene.workGroup, this.dimensionPreviewGroup);
  }

  preview(curveIds: string[], callback: (info: DimInfo) => void): void {
    this.unPreview();
    this.preCallback = callback;
    if (curveIds.length === 0) {
      return;
    }
    this.isPreview = true;
    const param = {
      points: [],
      objects: curveIds,
      master: true,
      curveIds,
    };
    this.renderDim(param);
  }

  unPreview() {
    this.isPreview = false;
    this.dimensionPreviewGroup.remove(this.previewDim);
    this.previewDim?.destroy();
    this.previewDim = null;
    this.preCallback = null;
    ctx2.viewer.requestRender();
  }

  setRenderFunc(dimData, skeData: ICurve[]): void {
    this.setSkeMap(skeData);
    this.renderDims(dimData);
    ctx2.viewer.requestRender();
  }

  setSkeMap(skeData: ICurve[]) {
    if (skeData?.length >= 0) {
      skeData?.forEach((item) => {
        const id = item.id.split(':')[2] ?? item.id;
        this.sketMap.set(id, { ...parseAxis(item), id });
      });
      this.formatEqualCons();
    }
  }

  // 整理相等约束的线的关系
  formatEqualCons() {
    const equalCons = {};
    this.sketMap.forEach((item) => {
      item.constraints?.forEach((cons) => {
        if (cons.type === 12) {
          if (!equalCons?.[cons.itemid]) {
            equalCons[cons.itemid] = [cons.itemid, cons.target];
          } else if (!equalCons[cons.itemid].includes(cons.target)) {
            equalCons[cons.itemid].push(cons.target);
          }
        }
      });
    });
    this.equalConArr = groupConnectedElements(Object.values(equalCons));
  }

  // 根据curve ids删除标注
  remove(curveIds: string[] | 'all' = 'all'): void {
    this.removeMap(curveIds);
    this.removeByCurveIds(curveIds);
    dimRender().unPreview();
  }

  // 根据curve id删除标注
  removeByCurveIds(curveIds: string[] | 'all'): void {
    const dimIds = [];
    this.getDimArrByIds(curveIds).forEach((dim) => {
      (dim as DimObject).destroy();
      dimIds.push(dim.userData.id);
    });
    this.removeMapByDimIds(dimIds);
    this.removeMap(curveIds);
  }

  removeMap(ids: string[] | 'all') {
    if (ids === 'all') {
      this.sketMap.clear();
    } else {
      ids.forEach((id) => {
        this.sketMap.delete(id);
      });
    }
  }

  // 根据标注ids删除标注
  removeByDimIds(dimIds: string[] | 'all'): void {
    this.getDimArrByDimIds(dimIds).forEach((dim) => {
      (dim as DimObject).destroy();
    });
    this.removeMapByDimIds(dimIds);
    setTimeout(() => ctx2.viewer.requestRender());
  }

  removeMapByDimIds(ids?: string[] | 'all') {
    if (ids === 'all') {
      this.dimObj = {};
      this.dimMap = new Map();
    } else {
      for (const id in this.dimObj) {
        if (ids.includes(this.dimObj[id])) {
          delete this.dimObj[id];
        }
      }
      ids.forEach((id) => {
        this.dimMap.delete(id);
      });
    }
    isDriven().setRectDims();
  }

  // 根据标注id获取标注
  getDimArrByDimIds(dimIds) {
    let needDeleteDim = null;
    if (dimIds === 'all') {
      needDeleteDim = this.dimensionGroup.children.filter((child) => {
        const dimInfo = child.userData;
        return dimInfo.id != null;
      });
    } else {
      needDeleteDim = this.dimensionGroup.children.filter((child) => {
        const dimInfo = child.userData;
        return dimInfo.id != null && dimIds.includes(dimInfo.id);
      });
    }
    return needDeleteDim;
  }

  // 根据curve id获取标注
  getDimArrByIds(curveIds) {
    let needDeleteDim = null;
    if (curveIds === 'all') {
      needDeleteDim = this.dimensionGroup.children.filter((child) => {
        const dimInfo = child.userData;
        return dimInfo.objects != null;
      });
    } else {
      needDeleteDim = this.dimensionGroup.children.filter((child) => {
        const dimInfo = child.userData;
        return (
          dimInfo.objects != null &&
          (curveIds.includes(dimInfo.objects?.[0]) ||
            curveIds.includes(dimInfo.objects?.[1]))
        );
      });
    }
    return needDeleteDim;
  }

  setUndisable(dimids: string[] = []): void {
    const needChange = this.dimensionGroup.children.filter((child) => {
      const dimInfo = child.userData;
      return dimids.includes(dimInfo.id);
    });
    needChange.forEach((dim) => {
      (dim as DimObject).unDisable();
    });
  }

  parseAnnotation(annotation: AnnotationData): DimInfo {
    return {
      ...annotation,
      objects:
        annotation.curve === annotation.target
          ? [annotation.curve]
          : [annotation.curve, annotation.target],
    };
  }

  renderDims(dimData: AnnotationData[]): void {
    if (dimData?.length >= 0) {
      dimData.forEach((annotation) => {
        this.renderDim(this.parseAnnotation(annotation));
      });
    }
  }

  // 根据curveIds获取对应的curve 信息
  getCurveInfo(item: Partial<DimInfo>) {
    const curveIds = item.objects;
    const vertices: Vector2tuple[][] = [];
    const curves: ICurve[] = [];

    const fullDimInfo = { ...item, curveIds, objects: [] } as DimInfo;

    curveIds.forEach((curveId) => {
      const curve = sketchStore.getState().curves.find((c) => c.id === curveId);
      curves.push(curve);

      if (curveIds.length === 1) {
        if (curve?.type === 'Line') {
          fullDimInfo.objects = curve.relative.slice(0, 2);
          fullDimInfo.curveIds = fullDimInfo.objects;
        } else {
          fullDimInfo.objects = [curveId];
        }

        if (this.isPreview) {
          if (curve?.type === 'Circle' || curve.type === 'ArcOfCircle') {
            fullDimInfo.value = (curve.params as ICircleParams).radius;
          }
        }

        if (['Line', 'Point'].includes(curve?.type)) {
          vertices.push(curve.vertices);
        }
      } else {
        if (['Line', 'Point'].includes(curve?.type)) {
          vertices.push(curve.vertices);
          fullDimInfo.objects.push(curveId);
        } else if (
          ['Circle', 'ArcOfCircle', 'Ellipse', 'ArcOfEllipse'].includes(
            curve?.type,
          )
        ) {
          const param = curve.params as
            | ICircleParams
            | IArcOfCircleParams
            | IEllipseParams;

          vertices.push([param.center]);
          fullDimInfo.objects.push(curve.relative[0]);
        }
      }
    });

    if (this.isPreview) {
      if (curves.length === 1) {
        fullDimInfo.type = {
          Line: AnnotationType.DistanceAnno,
          Circle: AnnotationType.RadiusAnno,
          ArcOfCircle: AnnotationType.RadiusAnno,
          Ellipse: AnnotationType.MajorRadiusAnno,
          ArcOfEllipse: AnnotationType.MajorRadiusAnno,
        }[curves[0].type];
      } else {
        if (curves.every((c) => c.type === 'Line')) {
          fullDimInfo.type = isParallel(
            new Vector2(...curves[0].vertices[1]).sub(
              new Vector2(...curves[0].vertices[0]),
            ),
            new Vector2(...curves[1].vertices[1]).sub(
              new Vector2(...curves[1].vertices[0]),
            ),
          )
            ? AnnotationType.DistanceAnno
            : AnnotationType.AngleAnno;
        } else {
          fullDimInfo.type = AnnotationType.DimensionAnno;
        }
      }
    }

    return {
      item: fullDimInfo,
      vertices,
      curves,
    };
  }

  getMapKey(item: DimInfo) {
    return item?.objects?.toSorted()?.join(',') + '-' + item.type;
  }

  // isExistDim(item: DimInfo) {
  //   return isDriven().isSecondaryDim(item);
  // }

  isExistDim(item: DimInfo) {
    return !isEmpty(this.dimObj[this.getMapKey(item)]);
  }

  setMapByKey(item: DimInfo) {
    this.dimObj[this.getMapKey(item)] = item.id;
  }

  // 判断标注是否存在
  isHasDim(id: string) {
    let flag = false;
    for (const key in this.dimObj) {
      if (this.dimObj[key] === id) {
        flag = true;
      }
    }
    return flag;
  }

  renderDim(rawItem: Partial<DimInfo>, Obj3D?: Object3D) {
    const { vertices, item, curves } = this.getCurveInfo(rawItem);
    if (this.isHasDim(item.id) || item.implict) {
      return null;
    }
    const { value, points, curveIds, objects } = item;
    if (
      objects.length === 2 &&
      item.type === AnnotationType.DimensionAnno &&
      this.isPreview
    ) {
      item.type = AnnotationType.DistanceAnno;
    }
    if (curves.length === 0 || (this.isPreview && this.isExistDim(item))) {
      return null;
    } else if (!this.isPreview) {
      this.setMapByKey(item);
      this.dimMap.set(item.id, item);
      isDriven().setRectDims();
    }

    let dim: DimObject = null;
    switch (item.type) {
      case AnnotationType.DimensionAnno:
      case AnnotationType.DistanceXAnno:
      case AnnotationType.DistanceYAnno:
      case AnnotationType.DistanceAnno:
        // 长度、X距离、Y距离
        dim = new DimLinear({
          dimConstraint: item.type,
          lineVertices: vertices,
          relPos: points,
          curveIds: objects,
          param: item.param,
          id: item.id,
          callback: (key, val, _, param) => {
            if (val != null) {
              const sign = value > 0 ? 1 : -1;
              this.labelCallback(key, val * sign, param, {
                ...item,
                // curveIds只有一个时为测量直线的长度，此时取两侧端点
                // 此处整体命名和数据流均需要重构
                objects: curveIds?.length === 1 ? objects : curveIds,
              });
            }
          },
        });
        break;
      case AnnotationType.RadiusAnno:
        {
          const curve = curves[0];
          const param = curve.params as ICircleParams | IArcOfCircleParams;
          const curveType = curve.type;

          const radiusAnnotationParams = {
            dimConstraint: item.type,
            center: new Vector3(param.center[0], param.center[1], 0),
            radius: value, // 半径
            relPos: points,
            curveIds: objects,
            curveType: curveType,
            callback: (key, val, _, param) => {
              this.labelCallback(key, val, param, item);
            },
            param: item.param,
            id: item.id,
            point1: null,
            point2: null,
          };
          if (points?.[1]?.[0] === 1) {
            dim = new DimDiameterTool(radiusAnnotationParams);
          } else {
            if (curveType === 'Circle') {
              dim = new DimDiameterTool(radiusAnnotationParams);
            } else if (curveType === 'ArcOfCircle') {
              const { point1, point2 } = param as IArcOfCircleParams;
              radiusAnnotationParams.point1 = new Vector3(
                point1[0],
                point1[1],
                0,
              );
              radiusAnnotationParams.point2 = new Vector3(
                point2[0],
                point2[1],
                0,
              );
              dim = new DimRadiusTool(radiusAnnotationParams);
            }
          }
        }
        break;
      case AnnotationType.AngleAnno: {
        const angleParams: DimAngleAttrs = {
          action: this.isPreview ? 'create' : 'update',
          dimConstraint: item.type,
          lineVertices: vertices,
          relPos: points,
          curveIds: objects,
          param: item.param,
          id: item.id,
          callback: (key, val, _, param) => {
            if (val != null) {
              const value = (dim as DimAngleTool)?.getDimDeg();
              item.points = [
                ...(dim as DimAngleTool).getPosLv(),
                ...(dim as DimAngleTool).getDimArcInfo(),
              ];
              if (curveIds != null) {
                item.objects = curveIds;
              }
              this.labelCallback(key, value, param, item);
            }
          },
        };
        dim = new DimAngleTool(angleParams);
        break;
      }
      case AnnotationType.MajorRadiusAnno:
      case AnnotationType.MinorRadiusAnno: {
        dim = new DimEllipseTool({
          dimConstraint: item.type,
          param: item.param,
          id: item.id,
          curveIds: objects,
          relPos: points,
          ...(curves[0].params as IEllipseParams),
          callback: (key, val, _, param) => {
            if (val != null) {
              this.labelCallback(key, val, param, item);
            }
          },
        });
        break;
      }
      default:
        dim = null;
        break;
    }
    if (dim) {
      dim?.labelFoucs();
      dim.userData = item;
      if (!item.master) {
        dim.setDisable();
      }
      if (!this.isPreview) {
        dim?.addTo(Obj3D || this.dimensionGroup);
      } else {
        this.previewDim = dim;
        this.previewDim?.addTo(this.dimensionPreviewGroup);
        this.previewDim.setDisableColor();
      }
    }
    return dim;
  }

  /**
   * @description:  传递参数的回调 接口请求参数 重新渲染标注
   * */
  async labelCallback(
    key: 'blur' | 'focus',
    value: number,
    param: string,
    item: DimInfo,
  ) {
    const { id, objects, type, points } = item;
    if (this.isPreview && key === 'blur') {
      let point = [];
      if (type === AnnotationType.AngleAnno) {
        point = [
          ...this.previewDim.getPosLv(),
          ...(this.previewDim as DimAngleTool).getDimArcInfo(),
        ];
      } else {
        point = this.previewDim?.getPosLv();
      }
      this.preCallback?.({
        id,
        objects,
        type,
        points: point,
        value,
        disabled: this.previewDim?.isDisabled,
        param,
      });
      this.unPreview();
    } else if (key === 'blur' && value != null && !isNaN(value)) {
      // param为空字符串为无公式
      this.labelBlurFun({
        annotation: id,
        value: param || value,
        points,
      })
        .then(() => {
          item.value = value;
          item.param = param;
          if (type === AnnotationType.RadiusAnno) {
            this.update(objects, [], { value, type, param });
          }
          this.renderDimValue(id, { value, param });
        })
        .catch(() => {
          this.renderDimValue(id);
        });
    }
  }

  // 渲染标注值
  renderDimValue(
    id: string,
    attrs?: {
      value: number;
      param: string;
    },
  ) {
    // 修改失败后重新渲染标注的值
    this.dimensionGroup.children.forEach((its) => {
      if (id === its.userData.id) {
        (its as DimObject).resetDimLabel(attrs?.value, attrs?.param);
      }
    });
  }

  update(
    curveIds: string[],
    curves?: ICurve[],
    dimInfo: { value: number; type: number; param?: string } = {
      value: null,
      type: null,
    },
  ): void {
    const { value, param } = dimInfo;
    if (curves) {
      this.setSkeMap(curves);
    }
    this.dimensionGroup.children.forEach((item) => {
      let curve = null;
      const userData = item.userData as DimInfo;
      const vertices = [];
      const flag = userData.objects.findIndex((id) => curveIds.includes(id));
      let ellipseParam = null;
      if (flag !== -1) {
        userData.objects?.forEach((id) => {
          curve = this.sketMap.get(id);
          if (curve?.type === 'Line' || curve?.type === 'Point') {
            vertices.push(curve.vertices);
          }
          if (curve?.type === 'Circle') {
            vertices.push(curve.params.center);
          }
          if (curve?.type === 'ArcOfCircle') {
            vertices.push(
              curve.params.point1,
              curve.params.point2,
              curve.params.center,
            );
          }
          if (curve?.type === 'Ellipse') {
            ellipseParam = curve.params;
          }
        });
        switch (item.userData.type) {
          case AnnotationType.DimensionAnno:
          case AnnotationType.DistanceXAnno:
          case AnnotationType.DistanceYAnno:
          case AnnotationType.DistanceAnno:
            (item as DimObject).update({
              lineVertices: vertices,
              param,
            });
            break;
          case AnnotationType.RadiusAnno:
            if (vertices.length === 1) {
              (item as DimObject).update({
                center: new Vector3(vertices[0][0], vertices[0][1], 0),
                radius: value,
                param,
              });
            } else if (vertices.length === 3) {
              (item as DimObject).update({
                point1: new Vector3(vertices[0][0], vertices[0][1], 0),
                point2: new Vector3(vertices[1][0], vertices[1][1], 0),
                center: new Vector3(vertices[2][0], vertices[2][1], 0),
                radius: value,
                param,
              });
            }
            break;
          case AnnotationType.AngleAnno:
            (item as DimObject).update({
              lineVertices: vertices,
              relPos: userData.points,
              param,
            });
            break;
          case AnnotationType.MajorRadiusAnno:
          case AnnotationType.MinorRadiusAnno:
            (item as DimEllipseTool).update({
              relPos: userData.points,
              ...ellipseParam,
              param,
            });
        }
      }
    });
    this.show(); // 重新渲染
  }

  hidden(): void {
    this.dimensionGroup.visible = false;
    this.dimensionGroup.children.forEach((item) => {
      (item as DimObject).dimLabel.object.visible = false;
    });
    ctx2.viewer.requestRender();
  }

  show(): void {
    this.dimensionGroup.visible = true;
    this.dimensionGroup.children.forEach((item) => {
      (item as DimObject).dimLabel.object.visible = true;
    });
    ctx2.viewer.requestRender();
  }

  getLineAngle(lineVertices) {
    const angles = lineVertices.map((item) =>
      getAngle(
        new Vector3(item[1][0], item[1][1], 0),
        new Vector3(item[0][0], item[0][1], 0),
      ),
    );
    const angleC = angles[0] + Math.PI * 2 - angles[1];
    const angle = MathTool.radToDeg(
      2 * Math.PI - (angleC > Math.PI * 2 ? angleC - Math.PI * 2 : angleC),
    );
    return angle;
  }
}
export const dimRender = () => {
  return DimRender.getInstance();
};
